package com.fowo.api.sys.component;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.fowo.api.sys.annotation.RedisSyncLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 多线程锁
 * 默认30秒超时等待
 * 测试：批量调用脚本方法
 * 10线程*10次执行达成率30%
 * 2线程*10次执行达成率100%
 */
@Aspect
@Component
public class RedisLockAop {

    private final IdentifierGenerator identifierGenerator = new DefaultIdentifierGenerator();

    @Pointcut(value = "@annotation(com.fowo.api.sys.annotation.RedisSyncLock)")
    public void pointcut() {
    }

    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RedisSyncLock redisSyncLock = method.getAnnotation(RedisSyncLock.class);
        //锁key、等待时间赋值，默认：类名方法名、30秒
        String lockKey;
        int timeout;
        if (ObjectUtil.isAllNotEmpty(redisSyncLock, redisSyncLock.value())) {
            lockKey = redisSyncLock.value();
        } else {
            lockKey = StrUtil.concat(true, joinPoint.getTarget().getClass().getName(), ".", signature.getName(), "()");
        }
        if (ObjectUtil.isAllNotEmpty(redisSyncLock, redisSyncLock.timeout())) {
            timeout = redisSyncLock.timeout();
        } else {
            timeout = 30;
        }
        Object res = null;
//        String clientId = identifierGenerator.nextUUID(null);
//        try {
//            Boolean lock = redisLock(lockKey, clientId);
//            if (lock) {
//                res = joinPoint.proceed();
//            }
//        } finally {
//            redisUnLock(lockKey, clientId);
//        }
        RLock lock = null;
        try {
            lock = redissonLock(lockKey, timeout);
            res = joinPoint.proceed();
        } finally {
            if (ObjectUtil.isNotEmpty(lock)) {
                redissonUnLock(lock);
            }
        }
        return res;
    }

    /*
     * redis 版本
    private Boolean redisLock(String lockKey, String clientId) {
        StringRedisTemplate stringRedislemplate = SpringUtil.getBean(StringRedisTemplate.class);
        if (ObjectUtil.isEmpty(stringRedislemplate)) {
            throw new RuntimeException("缓存配置错误，请联系管理员！");
        }
        Integer expireSeconds = 60 * 10;
        return stringRedislemplate.opsForValue().setIfAbsent(lockKey, clientId, expireSeconds, TimeUnit.SECONDS);
    }

    private Boolean redisUnLock(String lockKey, String clientId) {
        StringRedisTemplate stringRedislemplate = SpringUtil.getBean(StringRedisTemplate.class);
        if (ObjectUtil.isEmpty(stringRedislemplate)) {
            throw new RuntimeException("缓存配置错误，请联系管理员！");
        }
        if (StrUtil.equalsIgnoreCase(clientId, stringRedislemplate.opsForValue().get(lockKey))) {
            return stringRedislemplate.delete(lockKey);
        }
        return false;
    }
    */

    /*
     * redisson 版本
     */
    private RLock redissonLock(String lockKey, int timeout) throws InterruptedException {
        RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class);
        if (ObjectUtil.isEmpty(redissonClient)) {
            throw new RuntimeException("缓存配置错误，请稍后重试！");
        }
        RLock lock = redissonClient.getLock(lockKey);
        /**
         * waitTime：等待获取锁的最长时间。如果在等待时间内无法获取锁，并且没有其他锁释放，则返回 false。如果 waitTime < 0，则无限期等待，直到获得锁定。
         * leaseTime：就是redis key的过期时间，锁的持有时间，可以使用 ttl  查看过期时间。
         * 如果在持有时间结束前锁未被释放，则锁将自动过期，没有进行key续期，并且其他线程可以获得此锁。如果 leaseTime = 0，则锁将永久存在，直到被显式释放。
         */
        Boolean tryLock = lock.tryLock(timeout, timeout * 5, TimeUnit.SECONDS);
        if (!tryLock) {
            throw new RuntimeException("出了点问题，请稍后重试！");
        }
        return lock;
    }

    private void redissonUnLock(RLock lock) {
        lock.unlock();
    }

}
